﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using Microsoft.Win32;
using Microsoft.WindowsAPICodePack.Dialogs;
using NChardet;
using Ude;

namespace FFmpegVideoClip.Lib
{
    public class CUESheets
    {
        private static string OldPaht = string.Empty;
        public class FileTrack
        {
            public string FileName { get; set; } = string.Empty;
            public string FileFullPath { get; set; } = string.Empty;
            public List<TrackElement> tracks = new();
            public TimeSpan Duration = TimeSpan.Zero;
        }
        public class TrackElement
        {
            public int Index { get; set; }
            public string Title { get; set; } = string.Empty;
            public string PERFORMER { get; set; } = string.Empty;
            public string SONGWRITER { get; set; } = string.Empty;
            public TimeSpan Skip { get; set; } = TimeSpan.Zero;
            public TimeSpan Start { get; set; } = TimeSpan.Zero;
            public TimeSpan End { get; set; } = TimeSpan.Zero;

        }

        private const StringSplitOptions SplitOptions = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries;
        private static readonly char[] TimeSeparators = new char[] { ':', '：' };
        private static readonly char[] SplitSeparators = new char[] { '\u000a', '\u000d' };
        private static readonly char[] SplitBlankSeparators = new char[] { '\u0009', '\u000B', '\u0020', '\u3000', '\u00A0', '\u2006' };
        private static readonly char[] TrimSeparators = new char[] { '\u0009', '\u000B', '\u0020', '\u3000', '\u00A0', '\u2006', '\u0022', '\u201c', '\u201d', '\u2018', '\u2019', '\u0027' };
        private static readonly HashSet<char> TrimSeparatorsSet = new HashSet<char>(TrimSeparators);
        public List<FileTrack> FileTracks { get; set; } = new();
        public string Title { get; set; } = string.Empty;

        public void SetInit(Window? window)
        {
            foreach (FileTrack track in FileTracks)
                SetFilePaths(track, window);
            SetEndTime();
        }
        private void SetFilePaths(FileTrack track, Window? window)
        {
            if (!string.IsNullOrEmpty(OldPaht) )
            {
                if (File.Exists(OldPaht))
                    OldPaht = Path.GetDirectoryName(OldPaht)!;
                if (Directory.Exists(OldPaht))
                {
                    string tempPath = Path.Combine(OldPaht, track.FileName);
                    if (File.Exists(tempPath))
                    {
                        track.FileFullPath = tempPath;
                        return;
                    }
                }
            }
            
            while (true)
            {
                OpenFileDialog ofd = Tools.GetOpenFileDialog(OldPaht!, "匹配CUE文件", "*.*", "媒体文件(.*)|*.*");
                ofd.FileName = track.FileName;
                if (ofd.ShowDialog() ?? false)
                {
                    OldPaht = Path.GetDirectoryName(ofd.FileName) ?? string.Empty;
                    if (File.Exists(ofd.FileName))
                        track.FileFullPath = ofd.FileName;
                    else track.FileFullPath = string.Empty;
                }
                if (!File.Exists(track.FileFullPath))
                {
                    if (MessageBoxEx.Show(window, $"[{track.FileName}] 未匹配到有效文件，确认放弃么?", "警告", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No) == MessageBoxResult.Yes)
                        return;
                }
                else return;
            }
        }
        private void SetEndTime()
        {
            TrackElement current;
            TrackElement? next;
            int nextIndex;
            foreach (FileTrack Ftrack in FileTracks)
            {
                if (!File.Exists(Ftrack.FileFullPath))
                {
                    LogManage.WriteLineNewInfo($"读取CUE期间，关联的文件:[文件名:[{Ftrack.FileName}] 路径:[{Ftrack.FileFullPath}]] 不存在", true);
                    continue;
                }
                try { Ftrack.Duration = GetMediaInfo.GetInfo(Ftrack.FileFullPath).Duration; }
                catch { Ftrack.Duration = TimeSpan.Zero; }
                
                if (Ftrack.Duration < TimeSpan.Zero)
                    continue;
                if (Ftrack.tracks.Count < 0)
                    continue;
                Ftrack.tracks.Last().End = Ftrack.Duration;
                for (int i = 0; i < Ftrack.tracks.Count; i++)
                {
                    current = Ftrack.tracks[i];
                    if (current.End > TimeSpan.Zero)
                        continue;
                    nextIndex = i + 1;
                    if (nextIndex < Ftrack.tracks.Count)
                        next = Ftrack.tracks[nextIndex];
                    else next = null;

                    if (next == null)
                        continue;
                    else if (next.Skip > TimeSpan.Zero)
                        current.End = next.Skip;
                    else current.End = next.Start;
                }
            }
        }
        public static CUESheets From(string[] FileAllTextLine) 
        {
            CUESheets result = new();
            FileTrack? CurrentFTrack = null;
            TrackElement? CurrentTrack = null;
            int i = 0;
            for(; i < FileAllTextLine.Length; i++)               
            {
                if (FileAllTextLine[i].TrimStart().StartsWith("FILE", StringComparison.OrdinalIgnoreCase))
                    break;
                if (FileAllTextLine[i].TrimStart().StartsWith("title", StringComparison.OrdinalIgnoreCase))
                    result.Title = GetTrackElementVaue(FileAllTextLine[i]);
            }

            for (; i < FileAllTextLine.Length; i++)
            {
                if (FileAllTextLine[i].TrimStart().StartsWith("FILE", StringComparison.OrdinalIgnoreCase))
                {
                    CurrentFTrack = new();
                    result.FileTracks.Add(CurrentFTrack);
                    CurrentFTrack.FileName = GetTrackElementVaue(FileAllTextLine[i]);
                }
                if (FileAllTextLine[i].TrimStart().StartsWith("TRACK", StringComparison.OrdinalIgnoreCase))
                {
                    if (CurrentFTrack == null)
                        CurrentFTrack = new();
                    CurrentTrack = new();
                    CurrentFTrack.tracks.Add(CurrentTrack);
                }
                if (CurrentTrack != null)
                    SetValue(FileAllTextLine[i], CurrentTrack);
            }

            return result;
        }
        public static CUESheets From(string FileAllText) => From(FileAllText.Split(SplitSeparators, SplitOptions));
        private static CUESheets Load(string FilePath, Window? window)
        {
            if (!File.Exists(FilePath))
                throw new FileNotFoundException($"文件:[{FilePath}] 不存在");
            CUESheets Result = From(File.ReadAllLines(FilePath, GetEncoding(FilePath)));
            Result.SetInit(window);
            return Result;
        }
        public static string LoadFileInToClips(Window? window, string filePath)
        {
            if (File.Exists(filePath))
                OldPaht = filePath;
            CUESheets? sheets = null;
            sheets = Load(filePath, window);
            if (sheets == null)
                return string.Empty;

            string OutDirectory, inDirectory = Path.GetDirectoryName(filePath)!;

            using (CommonOpenFileDialog dialog = new CommonOpenFileDialog())
            {
                dialog.Title = "选择保存路径";
                dialog.IsFolderPicker = true;
                dialog.InitialDirectory = !string.IsNullOrEmpty(MediaElementConcrol.LastPath) ? MediaElementConcrol.LastPath :
                    (!string.IsNullOrEmpty(OldPaht) && Directory.Exists(OldPaht)) ? OldPaht : Environment.GetFolderPath(Environment.SpecialFolder.MyComputer);
                dialog.EnsurePathExists = true;
                if (dialog.ShowDialog() == CommonFileDialogResult.Cancel)
                    return string.Empty;
                OutDirectory = dialog.FileName;
            }

            foreach (FileTrack track in sheets.FileTracks)
                SetFileTrackToClips(sheets, track, inDirectory, OutDirectory);
            return sheets.FileTracks.FirstOrDefault()?.FileFullPath ?? string.Empty;
        }
        private static void SetFileTrackToClips(CUESheets sheets,FileTrack fileTrack,string inDirectory,string outDirectory)
        {
            if (string.IsNullOrEmpty(fileTrack.FileFullPath) || !File.Exists(fileTrack.FileFullPath))
                return;

            if (!(fileTrack.Duration > TimeSpan.Zero))
            {
                LogManage.WriteLineNewInfo($"读取CUE期间，关联的文件 [文件名:[{fileTrack.FileName}] 路径:[{fileTrack.FileFullPath}]] 配置信息错误，无法读取时长!", true);
                return;
            }
            GetMediaInfo.MediaInfoResult info = GetMediaInfo.GetInfo(fileTrack.FileFullPath);
            Clip CurrentClip;
            foreach (TrackElement item in fileTrack.tracks)
            {
                CurrentClip = new Clip();
                CurrentClip.OutDirectory = outDirectory;
                CurrentClip.Name = $"{item.Title}{(string.IsNullOrEmpty(item.PERFORMER)?string.Empty:$"-{item.PERFORMER}")
                    }{(string.IsNullOrEmpty(item.SONGWRITER) ? string.Empty : $"-{item.SONGWRITER}")
                    }{(string.IsNullOrEmpty(sheets.Title) ? string.Empty : $"-{sheets.Title}")}";
                CurrentClip.AddFragment(new()
                {
                    InputPath = fileTrack.FileFullPath,
                    Name = Path.GetFileNameWithoutExtension(fileTrack.FileFullPath),
                    StartTimeValue = Tools.GetTimeSpanToString(item.Start),
                    EndTimeValue = Tools.GetTimeSpanToString(item.End),
                    TempWritePath = inDirectory,
                    IsAudioStream = info.IsAudioStream,
                    IsVideoStream = info.IsVideoStream,
                    IsSubStream = info.IsSubStream
                }) ;
                ClipManage.ClipList.Add(CurrentClip);
            }
        }
        private static void SetValue(string TextRow, TrackElement element)
        {
            TextRow = TextRow.Trim(TrimSeparators);
            string ElementVaue = GetTrackElementVaue(TextRow);
            if (TextRow.StartsWith("TRACK", StringComparison.OrdinalIgnoreCase)) {
                if (int.TryParse(ElementVaue, out int value))
                    element.Index = value;
            }
            else if (TextRow.StartsWith("TITLE", StringComparison.OrdinalIgnoreCase))
                element.Title = ElementVaue;
            else if (TextRow.StartsWith("PERFORMER", StringComparison.OrdinalIgnoreCase))
                element.PERFORMER = ElementVaue;
            else if (TextRow.StartsWith("SONGWRITER", StringComparison.OrdinalIgnoreCase))
                element.SONGWRITER = ElementVaue;
            else if (TextRow.StartsWith("INDEX", StringComparison.OrdinalIgnoreCase))
            {
                if (int.TryParse(GetMiddleValue(TextRow), out int ModeFlag) && TryGetTrackTime(ElementVaue, out TimeSpan TimeValue))
                {
                    if (ModeFlag == 1)
                        element.Start = TimeValue;
                    else if (ModeFlag == 0)
                        element.Skip = TimeValue;
                }
            }
        }
        private static string GetTrackElementVaue(string TextRow)
        {
            TextRow = TextRow.Trim(SplitBlankSeparators);
            if (TextRow.StartsWith("TRACK",StringComparison.OrdinalIgnoreCase))
                return GetMiddleValue(TextRow);
            else if (TextRow.StartsWith("FILE", StringComparison.OrdinalIgnoreCase))
                return GetMiddleValue(TextRow);
            else if (TextRow.StartsWith("INDEX", StringComparison.OrdinalIgnoreCase))
                return GetLastValue(TextRow);
            else
                return GetSkipFirst(TextRow);
        }
        private static string GetMiddleValue(string TextRow)
        {
            int s = IndexOf(TextRow);
            if(s<0) return string.Empty;
            int e = LastIndexOf(TextRow);
            if(e<0) return string.Empty;
            return TextRow.Substring(s, e-s).Trim(TrimSeparators);
        }
        private static string GetLastValue(string TextRow)
        {
            int e = LastIndexOf(TextRow);
            if (e < 0) return string.Empty;
            return TextRow.Substring(e).Trim(TrimSeparators);
        }
        private static string GetSkipFirst(string TextRow)
        {
            int s = IndexOf(TextRow);
            if (s < 0) return string.Empty;
            return TextRow.Substring(s).Trim(TrimSeparators);
        }
        private static int IndexOf(string TextRow)
        {
            for (int i = 0; i < TextRow.Length; i++) 
            {
               if(TrimSeparatorsSet.Contains(TextRow[i]))
                    return i;
            }
            return -1;
        }
        private static int LastIndexOf(string TextRow)
        {
            for (int i = TextRow.Length - 1; i >= 0; i--)
            {
                if (TrimSeparatorsSet.Contains(TextRow[i]))
                    return i;
            }
            return -1;
        }
        private static bool TryGetTrackTime(string TimeVaue,out TimeSpan result)
        {
            result = TimeSpan.Zero;
            string[] values = TimeVaue.Split(TimeSeparators, SplitOptions);
            if (values.Length != 3)
            {
                LogManage.WriteLineNewInfo($"时间参数错误：{TimeVaue}",true);
                return false;
            }
            int m, s, f;
            if (int.TryParse(values[0], out m) && int.TryParse(values[1], out s) && int.TryParse(values[2], out f))
            {
                result = new TimeSpan(0, 0, m, s, f*10);
                return true;
            }
            return false;
        }
        
        private static Encoding GetEncoding(string FilePath)
        {
            Encoding? udeEncoding = null;
            Encoding nChardet = DetectorGetEncoding.Detector(FilePath, 2048);
            using (Stream stream = File.OpenRead(FilePath))
            {
                CharsetDetector cdet = new();
                cdet.Feed(stream);
                cdet.DataEnd();
                if (cdet.Charset != null)
                {
                    udeEncoding = Encoding.GetEncoding(cdet.Charset);
                    if (udeEncoding == nChardet)
                        return udeEncoding;
                    else if (cdet.Confidence > 0.75)
                        return udeEncoding;
                    else if (cdet.Confidence < 0.5 && nChardet.CodePage == 65001)
                        return Encoding.GetEncoding("GB18030");
                    else return nChardet;
                }
                else return nChardet;
            }
        }
    }
}
